﻿#include"XQFtp.h"
#include"qabstractsocket.h"
#include"XQFtpCommand.h"
#include"XQFtpDTP.h"
#include"XQFtpPI.h"
#include"XQLog.hpp"
#include<QDir>
#include<QCoreApplication>
#include<QTcpSocket>
#include<QTimer>
XQLog* XQFtp::m_log = XQLog::Create("XQFtp", LogType::Debug, XQLog::cmd_out | XQLog::cmd_cpp | XQLog::cmd_queue | XQLog::showTime| XQLog::showLogType | XQLog::showKey);
XQFtp::XQFtp(QObject *parent)
    : QObject(parent)
{
    m_pi = new XQFtpPI(this);
    m_pi->setDebug(&m_debug);
    connect(m_pi, &XQFtpPI::connectState,this, &XQFtp::_q_piConnectState);
    connect(m_pi, &XQFtpPI::finished,this,&XQFtp::_q_piFinished);
    connect(m_pi,QOverload<int, const QString&>::of(&XQFtpPI::error), this, &XQFtp::_q_piError);
    connect(m_pi, &XQFtpPI::rawFtpReply, this, &XQFtp::_q_piFtpReply);

    connect(m_pi->m_dtp, &XQFtpDTP::readyRead,
        this, &XQFtp::readyRead);
    connect(m_pi->m_dtp, &XQFtpDTP::dataTransferProgress,
        this, &XQFtp::dataTransferProgress);
    connect(m_pi->m_dtp, &XQFtpDTP::listInfo,
        this,QOverload<const XQUrlInfo&>::of(&XQFtp::listInfo));
    connect(m_pi->m_dtp, &XQFtpDTP::listInfo, [=](const XQUrlInfo& info){m_listInfo.append(info); });
 
}
int XQFtp::connectToHost(const QString &host, quint16 port)
{
    QStringList cmds;
    cmds << host;
    cmds << QString::number((uint)port);
    int id = addCommand(new XQFtpCommand(Ftp::ConnectToHost, cmds));
    m_pi->transferConnectionExtended = true;
    return id;
}
int XQFtp::login(const QString &user, const QString &password)
{
    QStringList cmds;
    cmds << (QLatin1String("USER ") + (user.isNull() ? QLatin1String("anonymous") : user) + QLatin1String("\r\n"));
    cmds << (QLatin1String("PASS ") + (password.isNull() ? QLatin1String("anonymous@") : password) + QLatin1String("\r\n"));
    return  addCommand(new XQFtpCommand(Ftp::Login, cmds));
}
int XQFtp::close()
{
    return  addCommand(new XQFtpCommand(Ftp::Close, QStringList(QLatin1String("QUIT\r\n"))));
}

int XQFtp::setTransferMode(Ftp::TransferMode mode)
{
    int id =  addCommand(new XQFtpCommand(Ftp::SetTransferMode, QStringList()));
    m_pi->transferConnectionExtended = true;
    m_transferMode = mode;
    return id;
}

/*!
    Enables use of the FTP proxy on host \a host and port \a
    port. Calling this function with \a host empty disables proxying.

    QFtp does not support FTP-over-HTTP proxy servers. Use
    QNetworkAccessManager for this.
*/
int XQFtp::setProxy(const QString &host, quint16 port)
{
    QStringList args;
    args << host << QString::number(port);
    return  addCommand(new XQFtpCommand(Ftp::SetProxy, args));
}

/*!
    Lists the contents of directory \a dir on the FTP server. If \a
    dir is empty, it lists the contents of the current directory.

    The listInfo() signal is emitted for each directory entry found.

    The function does not block and returns immediately. The command
    is scheduled, and its execution is performed asynchronously. The
    function returns a unique identifier which is passed by
    commandStarted() and commandFinished().

    When the command is started the commandStarted() signal is
    emitted. When it is finished the commandFinished() signal is
    emitted.

    \sa listInfo() commandStarted() commandFinished()
*/
int XQFtp::list(const QString &dir)
{
    QStringList cmds;
    cmds << QLatin1String("TYPE A\r\n");
    cmds << QLatin1String(m_transferMode == Ftp::Passive ? "PASV\r\n" : "PORT\r\n");
    if (dir.isEmpty())
        cmds << QLatin1String("LIST\r\n");
    else
        cmds << (QLatin1String("LIST ") + dir + QLatin1String("\r\n"));
    m_listInfo.clear();
    return  addCommand(new XQFtpCommand(Ftp::List, cmds));
}

/*!
    Changes the working directory of the server to \a dir.

    The function does not block and returns immediately. The command
    is scheduled, and its execution is performed asynchronously. The
    function returns a unique identifier which is passed by
    commandStarted() and commandFinished().

    When the command is started the commandStarted() signal is
    emitted. When it is finished the commandFinished() signal is
    emitted.

    \sa commandStarted() commandFinished()
*/
int XQFtp::cd(const QString &dir)
{
    return  addCommand(new XQFtpCommand(Ftp::Cd, QStringList(QLatin1String("CWD ") + dir + QLatin1String("\r\n"))));
}

/*!
    Downloads the file \a file from the server.

    If \a dev is 0, then the readyRead() signal is emitted when there
    is data available to read. You can then read the data with the
    read() or readAll() functions.

    If \a dev is not 0, the data is written directly to the device \a
    dev. Make sure that the \a dev pointer is valid for the duration
    of the operation (it is safe to delete it when the
    commandFinished() signal is emitted). In this case the readyRead()
    signal is \e not emitted and you cannot read data with the
    read() or readAll() functions.

    If you don't read the data immediately it becomes available, i.e.
    when the readyRead() signal is emitted, it is still available
    until the next command is started.

    For example, if you want to present the data to the user as soon
    as there is something available, connect to the readyRead() signal
    and read the data immediately. On the other hand, if you only want
    to work with the complete data, you can connect to the
    commandFinished() signal and read the data when the get() command
    is finished.

    The data is transferred as Binary or Ascii depending on the value
    of \a type.

    The function does not block and returns immediately. The command
    is scheduled, and its execution is performed asynchronously. The
    function returns a unique identifier which is passed by
    commandStarted() and commandFinished().

    When the command is started the commandStarted() signal is
    emitted. When it is finished the commandFinished() signal is
    emitted.

    \sa readyRead() dataTransferProgress() commandStarted()
    commandFinished()
*/
int XQFtp::get(const QString &file, QIODevice *dev, Ftp::TransferType type)
{
    QStringList cmds;
    if (type == Ftp::Binary)
        cmds << QLatin1String("TYPE I\r\n");
    else
        cmds << QLatin1String("TYPE A\r\n");
    cmds << QLatin1String("SIZE ") + file + QLatin1String("\r\n");
    cmds << QLatin1String(m_transferMode == Ftp::Passive ? "PASV\r\n" : "PORT\r\n");
    cmds << QLatin1String("RETR ") + file + QLatin1String("\r\n");
    return  addCommand(new XQFtpCommand(Ftp::Get, cmds, dev));
}

int XQFtp::get(const QString& remote_file, const QString& local_file, Ftp::TransferType type)
{
    auto path = QFileInfo(local_file).path();
    QDir().mkpath(path);//递归创建目录
    auto file = new QFile(local_file, this);
    connect(m_pi->m_dtp, &XQFtpDTP::finished, file,&QFile::deleteLater);
    connect(this, &XQFtp::destroyed, file, &QFile::deleteLater);
    file->open(QIODevice::OpenModeFlag::WriteOnly);
    return get(remote_file,file,type);
}

/*!
    \overload

    Writes a copy of the given \a data to the file called \a file on
    the server. The progress of the upload is reported by the
    dataTransferProgress() signal.

    The data is transferred as Binary or Ascii depending on the value
    of \a type.

    The function does not block and returns immediately. The command
    is scheduled, and its execution is performed asynchronously. The
    function returns a unique identifier which is passed by
    commandStarted() and commandFinished().

    When the command is started the commandStarted() signal is
    emitted. When it is finished the commandFinished() signal is
    emitted.

    Since this function takes a copy of the \a data, you can discard
    your own copy when this function returns.

    \sa dataTransferProgress() commandStarted() commandFinished()
*/
int XQFtp::put(const QByteArray &data, const QString &file, Ftp::TransferType type)
{
    QStringList cmds;
    if (type == Ftp::Binary)
        cmds << QLatin1String("TYPE I\r\n");
    else
        cmds << QLatin1String("TYPE A\r\n");
    cmds << QLatin1String(m_transferMode == Ftp::Passive ? "PASV\r\n" : "PORT\r\n");
    cmds << QLatin1String("ALLO ") + QString::number(data.size()) + QLatin1String("\r\n");
    cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n");
    return  addCommand(new XQFtpCommand(Ftp::Put, cmds, data));
}

/*!
    Reads the data from the IO device \a dev, and writes it to the
    file called \a file on the server. The data is read in chunks from
    the IO device, so this overload allows you to transmit large
    amounts of data without the need to read all the data into memory
    at once.

    The data is transferred as Binary or Ascii depending on the value
    of \a type.

    Make sure that the \a dev pointer is valid for the duration of the
    operation (it is safe to delete it when the commandFinished() is
    emitted).
*/
int XQFtp::put(QIODevice *dev, const QString &file, Ftp::TransferType type)
{
    QStringList cmds;
    if (type == Ftp::Binary)
        cmds << QLatin1String("TYPE I\r\n");
    else
        cmds << QLatin1String("TYPE A\r\n");
    cmds << QLatin1String(m_transferMode == Ftp::Passive ? "PASV\r\n" : "PORT\r\n");
    if (!dev->isSequential())
        cmds << QLatin1String("ALLO ") + QString::number(dev->size()) + QLatin1String("\r\n");
    cmds << QLatin1String("STOR ") + file + QLatin1String("\r\n");
    return  addCommand(new XQFtpCommand(Ftp::Put, cmds, dev));
}

int XQFtp::put(const QString& remote_file, const QString& local_file, Ftp::TransferType type)
{
    if (!QFile(local_file).exists())
        return -1;
    auto file = new QFile(local_file,this);
    file->open(QIODevice::OpenModeFlag::ReadOnly);
    connect(m_pi->m_dtp, &XQFtpDTP::finished, file,&QFile::deleteLater);
    connect(this, &XQFtp::destroyed, file, &QFile::deleteLater);
    return put(file, remote_file, type);
}

/*!
    Deletes the file called \a file from the server.

    The function does not block and returns immediately. The command
    is scheduled, and its execution is performed asynchronously. The
    function returns a unique identifier which is passed by
    commandStarted() and commandFinished().

    When the command is started the commandStarted() signal is
    emitted. When it is finished the commandFinished() signal is
    emitted.

    \sa commandStarted() commandFinished()
*/
int XQFtp::remove(const QString &file)
{
    return  addCommand(new XQFtpCommand(Ftp::Remove, QStringList(QLatin1String("DELE ") + file + QLatin1String("\r\n"))));
}

/*!
    Creates a directory called \a dir on the server.

    The function does not block and returns immediately. The command
    is scheduled, and its execution is performed asynchronously. The
    function returns a unique identifier which is passed by
    commandStarted() and commandFinished().

    When the command is started the commandStarted() signal is
    emitted. When it is finished the commandFinished() signal is
    emitted.

    \sa commandStarted() commandFinished()
*/
int XQFtp::mkdir(const QString &dir)
{
    return  addCommand(new XQFtpCommand(Ftp::Mkdir, QStringList(QLatin1String("MKD ") + dir + QLatin1String("\r\n"))));
}

/*!
    Removes the directory called \a dir from the server.

    The function does not block and returns immediately. The command
    is scheduled, and its execution is performed asynchronously. The
    function returns a unique identifier which is passed by
    commandStarted() and commandFinished().

    When the command is started the commandStarted() signal is
    emitted. When it is finished the commandFinished() signal is
    emitted.

    \sa commandStarted() commandFinished()
*/
int XQFtp::rmdir(const QString &dir)
{
    return  addCommand(new XQFtpCommand(Ftp::Rmdir, QStringList(QLatin1String("RMD ") + dir + QLatin1String("\r\n"))));
}

/*!
    Renames the file called \a oldname to \a newname on the server.

    The function does not block and returns immediately. The command
    is scheduled, and its execution is performed asynchronously. The
    function returns a unique identifier which is passed by
    commandStarted() and commandFinished().

    When the command is started the commandStarted() signal is
    emitted. When it is finished the commandFinished() signal is
    emitted.

    \sa commandStarted() commandFinished()
*/
int XQFtp::rename(const QString &oldname, const QString &newname)
{
    QStringList cmds;
    cmds << QLatin1String("RNFR ") + oldname + QLatin1String("\r\n");
    cmds << QLatin1String("RNTO ") + newname + QLatin1String("\r\n");
    return  addCommand(new XQFtpCommand(Ftp::Rename, cmds));
}

int XQFtp::move(const QString& oldfile, const QString& newfile)
{
    return rename(oldfile,newfile);
}

/*!
    Sends the raw FTP command \a command to the FTP server. This is
    useful for low-level FTP access. If the operation you wish to
    perform has an equivalent QFtp function, we recommend using the
    function instead of raw FTP commands since the functions are
    easier and safer.

    The function does not block and returns immediately. The command
    is scheduled, and its execution is performed asynchronously. The
    function returns a unique identifier which is passed by
    commandStarted() and commandFinished().

    When the command is started the commandStarted() signal is
    emitted. When it is finished the commandFinished() signal is
    emitted.

    \sa rawCommandReply() commandStarted() commandFinished()
*/
int XQFtp::rawCommand(const QString &command)
{
    QString cmd = command.trimmed() + QLatin1String("\r\n");
    return  addCommand(new XQFtpCommand(Ftp::RawCommand, QStringList(cmd)));
}

bool XQFtp::waitForStateChange(Ftp::State state, int msecs)
{
    QEventLoop loop;
    bool flag = false;
    auto con = connect(this, &XQFtp::stateChanged, [&](Ftp::State s) {
        if (s != state)
            return;
        flag = true;
        loop.quit();
        });
    //connect(m_pi, QOverload<int, const QString&>::of(&QFtpPI::error), &loop, &QEventLoop::quit);
    if (msecs > 0)
        QTimer::singleShot(msecs, &loop, &QEventLoop::quit);
    loop.exec();
    disconnect(con);
    return flag;
}

/*!
    Returns the number of bytes that can be read from the data socket
    at the moment.

    \sa get() readyRead() read() readAll()
*/
bool XQFtp::waitForConnected(int msecs)
{
    return waitForStateChange(Ftp::State::Connected, msecs);
}
bool XQFtp::waitForLoggedIn(int msecs)
{
    return waitForStateChange(Ftp::State::LoggedIn, msecs);
}
bool XQFtp::waitForFinished(int msecs)
{
    QEventLoop loop;
    bool flag = false;
    auto finished = connect(m_pi->m_dtp, &XQFtpDTP::finished, [&] {
        flag = true;
        loop.quit();
        });
    if(msecs>0)
        QTimer::singleShot(msecs, &loop, &QEventLoop::quit);
    loop.exec();
    disconnect(finished);
    return flag;
}
qint64 XQFtp::bytesAvailable() const
{
    return m_pi->m_dtp->bytesAvailable();
}

/*! \fn qint64 QFtp::readBlock(char *data, quint64 maxlen)

    Use read() instead.
*/

/*!
    Reads \a maxlen bytes from the data socket into \a data and
    returns the number of bytes read. Returns -1 if an error occurred.

    \sa get() readyRead() bytesAvailable() readAll()
*/
qint64 XQFtp::read(char *data, qint64 maxlen)
{
    return m_pi->m_dtp->read(data, maxlen);
}

/*!
    Reads all the bytes available from the data socket and returns
    them.

    \sa get() readyRead() bytesAvailable() read()
*/
QByteArray XQFtp::readAll()
{
    return m_pi->m_dtp->readAll();
}

const QList<XQUrlInfo>& XQFtp::listInfo() const
{
    return m_listInfo;
}

void XQFtp::setDebugModel(int model)
{
    m_debug = model;
}

void XQFtp::setDebugModel(Ftp::Debug model, bool open)
{
    if (open)
        m_debug |= model;
    else
        m_debug &= (~model);
}

/*!
    Aborts the current command and deletes all scheduled commands.

    If there is an unfinished command (i.e. a command for which the
    commandStarted() signal has been emitted, but for which the
    commandFinished() signal has not been emitted), this function
    sends an \c ABORT command to the server. When the server replies
    that the command is aborted, the commandFinished() signal with the
    \c error argument set to \c true is emitted for the command. Due
    to timing issues, it is possible that the command had already
    finished before the abort request reached the server, in which
    case, the commandFinished() signal is emitted with the \c error
    argument set to \c false.

    For all other commands that are affected by the abort(), no
    signals are emitted.

    If you don't start further FTP commands directly after the
    abort(), there won't be any scheduled commands and the done()
    signal is emitted.

    \warning Some FTP servers, for example the BSD FTP daemon (version
    0.3), wrongly return a positive reply even when an abort has
    occurred. For these servers the commandFinished() signal has its
    error flag set to \c false, even though the command did not
    complete successfully.

    \sa clearPendingCommands()
*/
void XQFtp::abort()
{
    if (m_pending.isEmpty())
        return;

    clearPendingCommands();
    m_pi->abort();
}

/*!
    Returns the identifier of the FTP command that is being executed
    or 0 if there is no command being executed.

    \sa currentCommand()
*/
int XQFtp::currentId() const
{
    if (m_pending.isEmpty())
        return 0;
    return m_pending.first()->m_id;
}

/*!
    Returns the command type of the FTP command being executed or \c
    None if there is no command being executed.

    \sa currentId()
*/
Ftp::Command XQFtp::currentCommand() const
{
    if (m_pending.isEmpty())
        return Ftp::None;
    return m_pending.first()->m_command;
}

/*!
    Returns the QIODevice pointer that is used by the FTP command to read data
    from or store data to. If there is no current FTP command being executed or
    if the command does not use an IO device, this function returns 0.

    This function can be used to delete the QIODevice in the slot connected to
    the commandFinished() signal.

    \sa get() put()
*/
QIODevice* XQFtp::currentDevice() const
{
    if (m_pending.isEmpty())
        return 0;
    XQFtpCommand *c = m_pending.first();
    if (c->m_is_ba)
        return 0;
    return c->m_data.dev;
}

/*!
    Returns true if there are any commands scheduled that have not yet
    been executed; otherwise returns false.

    The command that is being executed is \e not considered as a
    scheduled command.

    \sa clearPendingCommands() currentId() currentCommand()
*/
bool XQFtp::hasPendingCommands() const
{
    return m_pending.count() > 1;
}

/*!
    Deletes all pending commands from the list of scheduled commands.
    This does not affect the command that is being executed. If you
    want to stop this as well, use abort().

    \sa hasPendingCommands() abort()
*/
void XQFtp::clearPendingCommands()
{
    // delete all entires except the first one
    while (m_pending.count() > 1)
        delete m_pending.takeLast();
}

/*!
    Returns the current state of the object. When the state changes,
    the stateChanged() signal is emitted.

    \sa State stateChanged()
*/
Ftp::State XQFtp::state() const
{
    return  m_state;
}

/*!
    Returns the last error that occurred. This is useful to find out
    what went wrong when receiving a commandFinished() or a done()
    signal with the \c error argument set to \c true.

    If you start a new command, the error status is reset to \c NoError.
*/
Ftp::Error XQFtp::error() const
{
    return m_error;
}

/*!
    Returns a human-readable description of the last error that
    occurred. This is useful for presenting a error message to the
    user when receiving a commandFinished() or a done() signal with
    the \c error argument set to \c true.

    The error string is often (but not always) the reply from the
    server, so it is not always possible to translate the string. If
    the message comes from Qt, the string has already passed through
    tr().
*/
QString XQFtp::errorString() const
{
    return m_errorString;
}
bool XQFtp::isDebugModel(Ftp::Debug model)
{
    return  m_debug & model;
}
XQFtp::~XQFtp()
{
    abort();
    close();
}

QList<XQUrlInfo> XQFtp::List(const QString& host, quint16 port, const QString& dir, const QString& user, const QString& password)
{
    XQFtp ftp;
    ftp.connectToHost(host,port);
    ftp.login(user,password);
    ftp.list(dir);
    ftp.waitForFinished();
    return ftp.listInfo();
}

void XQFtp::Get(const QString& host, quint16 port, const QString& remote_file, const QString& local_file, Ftp::TransferType type, const QString& user, const QString& password)
{
    XQFtp ftp;
    ftp.connectToHost(host, port);
    ftp.login(user, password);
    ftp.get(remote_file,local_file,type);
    ftp.waitForFinished(0);
}

void XQFtp::Put(const QString& host, quint16 port, const QString& remote_file, const QString& local_file, Ftp::TransferType type, const QString& user, const QString& password)
{
    XQFtp ftp;
    ftp.connectToHost(host, port);
    ftp.login(user, password);
    ftp.put(remote_file, local_file, type);
    ftp.waitForFinished(0);
}

//QT_END_NAMESPACE



void XQFtp::_q_startNextCommand()
{
    if (m_pending.isEmpty())
        return;
    XQFtpCommand* c = m_pending.first();

    m_error = Ftp::NoError;
    m_errorString = "Unknown error";

    if (bytesAvailable())
        readAll(); //清理数据
    emit commandStarted(c->m_id);

    // Proxy support, replace the Login argument in place, then fall
    // through.
    if (c->m_command == Ftp::Login && !m_proxyHost.isEmpty()) {
        QString loginString = c->m_rawCmds.first().trimmed();
        loginString += QLatin1Char('@') + m_host;
        if (m_port && m_port != 21)
            loginString += QLatin1Char(':') + QString::number(m_port);
        loginString += QLatin1String("\r\n");
        c->m_rawCmds[0] = loginString;
    }

    if (c->m_command == Ftp::SetTransferMode) {
        _q_piFinished(QLatin1String("Transfer mode set"));
    }
    else if (c->m_command == Ftp::SetProxy) {
        m_proxyHost = c->m_rawCmds[0];
        m_proxyPort = c->m_rawCmds[1].toUInt();
        c->m_rawCmds.clear();
        _q_piFinished(QLatin1String("Proxy set to ") + m_proxyHost + QLatin1Char(':') + QString::number(m_proxyPort));
    }
    else if (c->m_command == Ftp::ConnectToHost) {
#ifndef QT_NO_BEARERMANAGEMENT
        //copy network session down to the PI
        m_pi->setProperty("_q_networksession", property("_q_networksession"));
#endif
        if (!m_proxyHost.isEmpty()) {
            m_host = c->m_rawCmds[0];
            m_port = c->m_rawCmds[1].toUInt();
            m_pi->connectToHost(m_proxyHost, m_proxyPort);
        }
        else {
            m_pi->connectToHost(c->m_rawCmds[0], c->m_rawCmds[1].toUInt());
        }
    }
    else {
        if (c->m_command == Ftp::Put) {
            if (c->m_is_ba) {
                m_pi->m_dtp->setData(c->m_data.ba);
                m_pi->m_dtp->setBytesTotal(c->m_data.ba->size());
            }
            else if (c->m_data.dev && (c->m_data.dev->isOpen() || c->m_data.dev->open(QIODevice::ReadOnly))) {
                m_pi->m_dtp->setDevice(c->m_data.dev);
                if (c->m_data.dev->isSequential()) {
                    m_pi->m_dtp->setBytesTotal(0);
                    connect(c->m_data.dev,&QIODevice::readyRead, m_pi->m_dtp, &XQFtpDTP::dataReadyRead);
                    connect(c->m_data.dev, &QIODevice::readChannelFinished, m_pi->m_dtp, &XQFtpDTP::dataReadyRead);
                    //m_pi->dtp->connect(c->data.dev, SIGNAL(readyRead()), SLOT(dataReadyRead()));
                    //m_pi->dtp->connect(c->data.dev, SIGNAL(readChannelFinished()), SLOT(dataReadyRead()));
                }
                else {
                    m_pi->m_dtp->setBytesTotal(c->m_data.dev->size());
                }
            }
        }
        else if (c->m_command == Ftp::Get) {
            if (!c->m_is_ba && c->m_data.dev) {
                m_pi->m_dtp->setDevice(c->m_data.dev);
            }
        }
        else if (c->m_command == Ftp::Close) {
            m_state = Ftp::Closing;
            emit stateChanged(m_state);
        }
        m_pi->sendCommands(c->m_rawCmds);
    }
}

void XQFtp::_q_piFinished(const QString& text)
{
    if (m_pending.isEmpty())
        return;
    XQFtpCommand* c = m_pending.first();

    if (c->m_command == Ftp::Close) {
        // The order of in which the slots are called is arbitrary, so
        // disconnect the SIGNAL-SIGNAL temporary to make sure that we
        // don't get the commandFinished() signal before the stateChanged()
        // signal.
        if (m_state != Ftp::Unconnected) {
            m_close_waitForStateChange = true;
            return;
        }
    }
    emit commandFinished(c->m_id, false);
    m_pending.removeFirst();

    delete c;

    if (m_pending.isEmpty()) {
        emit done(false);
    }
    else {
        _q_startNextCommand();
    }
}

void XQFtp::_q_piError(int errorCode, const QString& text)
{
    if (m_pending.isEmpty()) {
        qWarning("QFtpPrivate::_q_piError was called without pending command!");
        return;
    }

    XQFtpCommand* c = m_pending.first();

    // non-fatal errors
    if (c->m_command == Ftp::Get && m_pi->currentCommand().startsWith(QLatin1String("SIZE "))) {
        m_pi->m_dtp->setBytesTotal(0);
        return;
    }
    else if (c->m_command == Ftp::Put && m_pi->currentCommand().startsWith(QLatin1String("ALLO "))) {
        return;
    }

    m_error = Ftp::Error(errorCode);
    switch (currentCommand()) {
    case Ftp::ConnectToHost:
        m_errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Connecting to host failed:\n%1"))
            .arg(text);
        break;
    case Ftp::Login:
        m_errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Login failed:\n%1"))
            .arg(text);
        break;
    case Ftp::List:
        m_errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Listing directory failed:\n%1"))
            .arg(text);
        break;
    case Ftp::Cd:
        m_errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Changing directory failed:\n%1"))
            .arg(text);
        break;
    case Ftp::Get:
        m_errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Downloading file failed:\n%1"))
            .arg(text);
        break;
    case Ftp::Put:
        m_errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Uploading file failed:\n%1"))
            .arg(text);
        break;
    case Ftp::Remove:
        m_errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Removing file failed:\n%1"))
            .arg(text);
        break;
    case Ftp::Mkdir:
        m_errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Creating directory failed:\n%1"))
            .arg(text);
        break;
    case Ftp::Rmdir:
        m_errorString = QString::fromLatin1(QT_TRANSLATE_NOOP("QFtp", "Removing directory failed:\n%1"))
            .arg(text);
        break;
    default:
        m_errorString = text;
        break;
    }

    m_pi->clearPendingCommands();
    //clearPendingCommands();
    emit commandFinished(c->m_id, true);

    m_pending.removeFirst();
    delete c;
    if (m_pending.isEmpty())
        emit done(true);
    else
        _q_startNextCommand();
}

void XQFtp::_q_piConnectState(int connectState)
{
    m_state = Ftp::State(connectState);
    emit stateChanged(state());
    if (m_close_waitForStateChange) {
        m_close_waitForStateChange = false;
        _q_piFinished(QLatin1String(QT_TRANSLATE_NOOP("QFtp", "Connection closed")));
    }
}

void XQFtp::_q_piFtpReply(int code, const QString&text)
{
    if (currentCommand() == Ftp::RawCommand) {
        m_pi->rawCommand = true;
        emit rawCommandReply(code, text);
    }
}

int XQFtp::addCommand(XQFtpCommand* cmd)
{
    m_pending.append(cmd);

    if (m_pending.count() == 1) {
        // 在ID返回之前不要发出commandStarted()信号
        QTimer::singleShot(0, this, &XQFtp::_q_startNextCommand);
    }
    return cmd->m_id;
}